/* Object3D 通用操作 */
import * as THREE from "three";

import type { SceneNodeData } from "./types";
import { tree } from "./tree";
import { layer, object3d } from "./index";

// MARK: 设置 -------------------------------------------------------------------------------------

// FIXME 此处保存了状态，那它就不是纯函数了
let layerPickable = 2;

/**
 * 设置可拾取图层的数值
 * @param layer 图层值
 */
export function setLayerPickable(layer: number) {
  layerPickable = layer;
}

// MARK: 显隐 -------------------------------------------------------------------------------------

/**
 * 显示物体（还需要让它能被选中）
 * @param object 要切换显示的物体
 */
export function showObject(object?: THREE.Object3D) {
  toggleObjectVisible(object, true);
}

/**
 * 隐藏物体（还需要让它不能被选中）
 * @param object 要隐藏的物体
 */
export function hideObject(object?: THREE.Object3D) {
  toggleObjectVisible(object, false);
}

/**
 * 切换物体显隐（还需要考虑它是否能被选中）
 * @param object 要切换显隐的物体
 * @param toVisible 是否是要切换到显示
 */
export function toggleObjectVisible(
  object: THREE.Object3D | undefined,
  toVisible: boolean
) {
  if (object != null) {
    // 设置物体的显隐
    object.visible = toVisible;

    // 设置物体的可拾取图层
    if (toVisible) layer.enable(object, layerPickable);
    else layer.disable(object, layerPickable);
  }
}

// MARK: 类型判断 ---------------------------------------------------------------------------------

/**
 * 物体是否为场景节点（在世界大纲中会出现的节点，目前只有 Model 和 Group 两种）
 * @param object 要判断的物体
 * @returns 物体是否是场景节点
 */
export function isObjectSceneNode(object?: THREE.Object3D) {
  if (object == null) return false;

  const userData = object.userData as SceneNodeData;
  return userData.isModel || userData.isGroup || false;
}

/**
 * 判断物体是否为模型物体节点
 * @param object 要判断的物体
 * @returns 判断结果
 */
export function isObjectModel(object?: THREE.Object3D) {
  if (object == null) return false;

  const userData = object.userData as SceneNodeData;
  return userData.isModel || false;
}

/**
 * 物体是否为组节点
 * @param object 要判断的物体
 * @returns 判断结果
 */
export function isObjectGroup(object?: THREE.Object3D) {
  if (object == null) return false;

  const userData = object.userData as SceneNodeData;
  return userData.isGroup || false;
}

// MARK: 查找 -------------------------------------------------------------------------------------

/**
 * 按 UUID 查找物体
 * @param group 物体所在的容器
 * @param uuid 物体的 UUID
 * @returns 找到的物体
 */
export function findModelByUuid(
  group: THREE.Object3D,
  uuid: string
): THREE.Object3D | undefined {
  if (group == null) return undefined;

  let object: any = undefined;
  object3d.traverseWhen(
    group,
    (node: THREE.Object3D) => {
      if (isObjectSceneNode(node) && node.uuid == uuid) object = node;
    },
    (node: THREE.Object3D) => {
      const userData = node.userData as SceneNodeData;
      return userData.isModelGroup || isObjectSceneNode(node);
    }
  );
  return object;
}

/**
 * 按编号（Code）查找物体
 * @param group 物体所在的容器
 * @param code 物体的编号
 * @returns 找到的物体
 */
export function findModelByCode(
  group: THREE.Object3D,
  code: string
): THREE.Object3D | undefined {
  if (group == null) return undefined;

  let object: any = undefined;
  object3d.traverseWhen(
    group,
    (node: THREE.Object3D) => {
      const userData = node.userData as SceneNodeData;
      if (isObjectSceneNode(node) && userData.code == code) object = node;
    },
    (node: THREE.Object3D) => {
      const userData = node.userData as SceneNodeData;
      return userData.isModelGroup || isObjectSceneNode(node);
    }
  );
  return object;
}

/**
 * 根据 index 从物体中查找网格体
 * @param object 网格体所在的根节点物体
 * @returns 找到的网格体
 */
export function findObjectMeshByIndex(
  object: THREE.Object3D,
  meshIndex: number
): THREE.Mesh | undefined {
  const dfsMeshed: THREE.Mesh[] = [];
  tree.traverse(object, (item: any) => {
    const maybeMesh = item.node as THREE.Mesh;
    if (maybeMesh.isMesh) dfsMeshed.push(maybeMesh);
  });
  // console.log('findObjectMeshByIndex.dfsMeshed', dfsMeshed)
  return dfsMeshed[meshIndex];
}

/**
 * 获取一个网格体在一个物体中的索引，不在内部的话返回 -1
 * @param mesh 目标网格体
 * @param object 网格体所在物体
 * @returns 网格体在物体中的索引
 */
export function getMeshIndexInObject(
  mesh: THREE.Mesh,
  object: THREE.Object3D
): number {
  const dfsMeshed: THREE.Mesh[] = [];
  tree.traverse(object, (item: any) => {
    const maybeMesh = item.node as THREE.Mesh;
    if (maybeMesh.isMesh) dfsMeshed.push(maybeMesh);
  });
  // console.log('findObjectMeshByIndex.dfsMeshed', dfsMeshed)
  return dfsMeshed.indexOf(mesh);
}
